# $Id: ConfigCopy.py 7557b5d4d82b 2012/12/02 09:07:16 pgurumur $
# Copyright (c) 2009 Prabhu Gurumurthy  <pgurumur@gmail.com>
# 
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

# Backup the IOS based on following website:
# http://www.cisco.com/en/US/tech/tk648/tk362/technologies_configuration_example09186a0080094aa6.shtml
# These MIBs are used: 
# CISCO-SMI-V1SMI.my 
# SNMPv2-TC-V1SMI.my 
# CISCO-CONFIG-COPY-MIB-V1SMI.my 
# CISCO-FLASH-MIB.my 
# ccCopyEntryRowStatus 
#     TYPE : integer 
#        VALUES : createAndGo(4) : Create an entry 
#                 destroy(6) : Delete an entry 
#
# ccCopyProtocol 
#     TYPE : integer 
#        VALUES : tftp(1) : To use tftp as protocol to copy
#                 rcp(3) : To use RCP as protocol to copy 
# 
# ccCopySourceFileType: specifies the type of file to copy from. 
#     TYPE : integer 
#        VALUES : networkFile(1) 
#                 startupConfig(3) 
#                 runningConfig(4)
#
# ccCopyDestFileType: specifies the type of file to copy to. 
#     TYPE : integer 
#        VALUES : networkFile(1) 
#                 startupConfig(3) 
#                 runningConfig(4)
#
# ccCopyServerAddress: The IP address of the (TFTP) server from 
#                      (or to) which to copy the configuration 
#                      file. 
#     TYPE : ipaddress 
#        VALUES : Any valid ip address xxx.xxx.xxx.xxx 
#
# ccCopyFileName 
#     TYPE : octetstring 
#        VALUES : The file name (including the path, if applicable)
#                 of the file. The file name is referred to as 
#                 <filename>. 
# 
# Example for cmd line:
# snmpset -v 1 -c private <device name> 
# .1.3.6.1.4.1.9.9.96.1.1.1.1.2.<Random number> integer 1 
# .1.3.6.1.4.1.9.9.96.1.1.1.1.3.<Random number> integer 4 
# .1.3.6.1.4.1.9.9.96.1.1.1.1.4.<Random number> integer 1 
# .1.3.6.1.4.1.9.9.96.1.1.1.1.5.<Random number> ipaddress 
#                             "<server ip address>" 
# .1.3.6.1.4.1.9.9.96.1.1.1.1.6.<Random number> octetstring 
#                             "<file name>" 
# .1.3.6.1.4.1.9.9.96.1.1.1.1.14.<Random number> integer 4 
#

import os
import tempfile
import time
import socket
import shutil

from django.core.exceptions import ObjectDoesNotExist
from djanet.compartment import models
from djanet.lib import exception, ip, snmp, sshlib, stdlib, telnet

__version__ =  "$Revision: 7557b5d4d82b $"
__author__ = "$Author: pgurumur $"

class ConfigCopy(object):
   def __init__(self, Device, Type, Sleep = 120):
      super(ConfigCopy, self).__init__()
      self._err = exception.ConfigCopyError()

      self._snmp = None
      self._host = None
      self._community = None
      self._user = None
      self._passwd = None
      self._cfg = None
      self._tftp = None
      self._login = None
      self._tempfile = None
      self._type = Type
      self._telnet = False
      self._newfile = None

      if Sleep:
         self._sleep = Sleep
      else:
         self._sleep = 300

      self._host = Device
      if self._type is None:
         self._error("Device type not found!")

      try:
         location = models.DeviceLocation.objects.get(device = self._host.id)
         if location:
            self._community = models.Compartment.objects.get(
                  id = location.compartment).snmp_write

         cred = models.DeviceLogin.objects.get(
               id = models.ConfigBackup.objects.get(device = self._host.id))
         self._user = str(cred.username)
         self._passwd = str(cred.password)
         method = str(cred.login_method)

         if self._type == "IOS" and self._community:
            self._snmpConnect()
         else:
            if method.lower() == "ssh":
               self._sshConnect()
            elif method.lower() == "telnet":
               self._telnetConnect()
      except ObjectDoesNotExist, message:
         self._error(message)

      try:
         self._tftp = ip.ip(int(models.BackupInfo.objects.get(
            device = self._host.id).tftp_server), version = 4)
      except ObjectDoesNotExist, message:
         try:
            self._tftp = socket.gethostbyaddr(socket.gethostname())[-1][0]
         except socket.gaierror, (error, message):
            self._error(message)

      if self._tftp:
         self._tftp = str(self._tftp)
         if self._tftp == "127.0.0.1":
            self._error("invalid TFTP server")
      else:
         self._error("cannot proceed without tftp server!")

   close = property(fget = lambda self: self.Close())

   def Close(self):
      if self._login:
         try:
            self._login.close
         except EOFError:
            pass
         finally:
            self._login = None

      if self._tempfile:
         try:
            os.unlink(self._tempfile)
         except OSError, message:
            pass

      if self._newfile:
         try:
            os.unlink(self._newfile)
         except OSError as err:
            pass

   def _sanitize(self):
      retval = 0
      if self._cfg:
         (fh, self._newfile) = tempfile.mkstemp()
         nfh = None
         ofh = None

         try:
            nfh = open(self._newfile, "w")
            ofh = open(self._cfg, "r")
         except IOError as err:
            self._error(err.message)
         except OSError as err:
            self._error(err.message)
         else:
            for line in ofh.readlines():
               if self._type in ("ASA", "FWSM"):
                  if "Written by" not in line:
                     nfh.write(line)
               elif self._type == "IOS":
                  if "ntp clock-period" not in line:
                     nfh.write(line)
         finally:
            ofh.close()
            nfh.close()

         info = None
         try:
            info = os.stat(self._newfile)
         except OSError as err:
            self._error(err.message)
         else:
            if info.st_size:
               try:
                  os.unlink(self._cfg)
                  shutil.move(self._newfile, self._cfg)
                  self._newfile = None
                  retval = info.st_size
               except OSError as err:
                  self._error(err.message)

         if self._newfile:
            try:
               os.unlink(self._newfile)
            except OSError as err:
               pass

      return retval

   def _snmpConnect(self):
      try:
         self._snmp = snmp.snmp(server = str(self._host.ip),
               community = self._community)
      except exception.SNMPError, message:
         self._error(message)

   def _sshConnect(self):
      try:
         self._login = sshlib.SSHClient(
               host = str(self._host.ip), user = self._user,
               passwd = self._passwd)
      except exception.SSHException, message:
         self._error(message)
      except EOFError:
         raise

   def _telnetConnect(self):
      try:
         self._login = telnet.telnet(str(self._host.ip))
      except exception.TelnetException, message:
         self._error(message)
      else:
         self._login.Auth(self._user, self._passwd)
         self._telnet = True

   def _error(self, fmt, *args):
      self.close
      raise exception.ConfigCopyError(stdlib.sprintf(fmt, *args))

   def __str__(self):
      return stdlib.my_str(self._host)

   def __repr__(self):
      return repr(str(self))

   def __unicode__(self):
      return unicode(str(self))

   def GetSysDescr(self):
      retval = None
      oid = ('sysDescr', 0)
      if self._snmp:
         try:
            retval = self._snmp.Get(oid)
         except exception.SNMPError, message:
            self._error(message)

      return retval

   def tftpInit(self):
      (fileno, temp) = tempfile.mkstemp(dir = "/tftpboot")
      if os.path.exists(temp):
         self._cfg = "/tftpboot/%s.cfg" %str(self._host.ip)
         try:
            os.rename(temp, self._cfg)
            os.chmod(self._cfg, 0666)
         except OSError, message:
            os.unlink(temp)
            raise exception.ConfigCopyError, message

   def GetConfigFile(self):
      return self._cfg

   def GetConfig(self, UseSnmp = True):
      retval = 0
      config = self._cfg.split("/")[2]
      if UseSnmp:
         if self._snmp:
            # Entry status
            self._snmp.Set((1,3,6,1,4,1,9,9,96,1,1,1,1,14,1),
                  self._snmp.integer(6))
   
            # Copy protocol (using tftp)
            self._snmp.Set((1,3,6,1,4,1,9,9,96,1,1,1,1,2,1),
                  self._snmp.integer(1))

            # Copy source file type ("running config")
            self._snmp.Set((1,3,6,1,4,1,9,9,96,1,1,1,1,3,1),
                  self._snmp.integer(4))

            # Copy destination file (remote)
            self._snmp.Set((1,3,6,1,4,1,9,9,96,1,1,1,1,4,1),
                  self._snmp.integer(1))

            # TFTP server address
            self._snmp.Set((1,3,6,1,4,1,9,9,96,1,1,1,1,5,1),
                  self._snmp.ipaddress(self._tftp))

            # File string
            self._snmp.Set((1,3,6,1,4,1,9,9,96,1,1,1,1,6,1),
                  self._snmp.string(config))

            # Entry status
            self._snmp.Set((1,3,6,1,4,1,9,9,96,1,1,1,1,14,1),
                  self._snmp.integer(1))
      else:
         if self._login:
            (fileno, ftemp) = tempfile.mkstemp()
            if ftemp and os.path.exists(ftemp) and self._cfg:
               try:
                  fh = open(ftemp, "w")
               except IOError, message:
                  self._error(message)
               else:
                  if (self._type == "ASA") or (self._type == "FWSM"):
                     fh.write("enable\n")
                     fh.write("%s\n" %self._passwd)
                     fh.write("terminal pager 0\n")
                     fh.write("copy /noconfirm running-config tftp://%s/%s\n" %(
                        self._tftp, config))
                     fh.write("terminal pager 24\n")
                     fh.write("exit\n")
                  elif (self._type == "IOS") or (self._type == "NXOS"):
                     fh.write("copy running-config tftp://%s/%s\n" %(
                        self._tftp, config))
                     if self._type == "IOS":
                        fh.write("\n")

                     fh.write("\n")
                     fh.write("exit\n")
                     fh.close()

               if self._telnet:
                  self._login.SendFile(ftemp)
               else:
                  try:
                     self._login.GetChannel(ftemp, False)
                  except exception.SSHException, message:
                     os.unlink(ftemp)
                     self._error(message)

            self.close

      #Wait
      time.sleep(self._sleep)
      try:
         retval = self._sanitize()
         if retval == 0:
            os.unlink(self._cfg)
      except OSError as err:
         pass

      return retval
